/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-登录通-缺省实现
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.dlt.webapi.handler;

import com.taiping.dlt.converter.AuthLoginBOConverter;
import com.taiping.dlt.manager.cache.LocalSessionService;
import com.taiping.dlt.pojo.bo.AuthLoginBO;
import com.taiping.dlt.service.DltService;
import com.taipingframework.boot.dlt.AuthorizeActionCallback;
import com.taiping.dlt.pojo.query.AuthLoginQO;
import com.taipingframework.utility.exception.SystemInterrupterException;
import com.taipingframework.boot.web.response.result.ApiResult;
import com.taipingframework.boot.web.response.status.ApiStatusEnum;
import com.taipingframework.utility.exception.ApplicationException;
import com.taipingframework.utility.extend.ReactorHelper;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.function.Function;

/**
 * 网络参考
 * https://www.cnblogs.com/limuma/p/9315343.html
 */
@Component
@RequiredArgsConstructor
public class DltHandler {

    private final LocalSessionService sessionService;
    private final DltService dltService;
    private final AuthorizeActionCallback authorizeActionCallback;

    /**
     * 判断当前会话是否处于登录状态/判断前端拿到的token是否有效
     *
     * @return true - token有效；false - token过期
     */
    public Mono<ApiResult<Boolean>> check() {
        return sessionService.loginCheck()
                .zipWith(ApiResult.<Boolean>create(),
                        (isUnexpired, apiResult) -> apiResult
                                .setStatus(ApiStatusEnum.SUCCESS)
                                .setResult(isUnexpired)
                ).onErrorResume(e -> Mono.error(new ApplicationException(e)));
    }

    /**
     * 登录
     */
    public Mono<ApiResult<String>> login(AuthLoginQO loginQO) {
        if (StringUtils.isBlank(loginQO.getAccount())) {
            //return ApiResult.<Boolean>create().map(apiResult -> apiResult.setStatus(ApiStatusEnum.ERR_A0110).setResult(false));
            return Mono.error(new SystemInterrupterException(ApiStatusEnum.ERR_A0110));
        }
        if (StringUtils.isBlank(loginQO.getPassword())) {
            //return ApiResult.<Boolean>create().map(apiResult -> apiResult.setStatus(ApiStatusEnum.ERR_A0120).setResult(false));
            return Mono.error(new SystemInterrupterException(ApiStatusEnum.ERR_A0120));
        }

        // 直接调用登录接口，如果是重复的登录操作，每次都将执行会话验证逻辑与数据查询逻辑，影响登录接口的性能。
        Function<AuthLoginQO, Mono<String>> loginAction = (authLoginQO) -> {
            AuthLoginBO authLoginBO = AuthLoginBOConverter.convert(authLoginQO);
            return dltService.login(authLoginBO)
                    .flatMap(sessionService::createLocalSession)
                    .flatMap(sessionId -> authorizeActionCallback.loginCallback().thenReturn(sessionId))
                    .onErrorResume(e -> Mono.error(new ApplicationException(e)));
        };

        // 此处，根据登录账户和前端会话标识token直接调用方法sessionService.loginValidate()判断是否提前返回结果！
        return ReactorHelper.getExchange()
                .flatMap(exchange -> sessionService.loginValidate(loginQO.getAccount()))
                .flatMap(sid -> StringUtils.isEmpty(sid) ? loginAction.apply(loginQO) : Mono.just(sid))
                .zipWith(ApiResult.<String>create(), (sessionId, result) -> result.setStatus(ApiStatusEnum.SUCCESS).setResult(sessionId))
                .onErrorResume(e -> Mono.error(new ApplicationException(e)));
        /* 重复登录，会导致登录逻辑重复执行，仅用于调试目的：
        AuthLoginBO authLoginBO = AuthLoginBOConverter.convert(loginQO);
        return dltService.login(authLoginBO)
                .flatMap(sessionService::createLocalSession)
                .zipWith(ApiResult.<String>create(), (flag, result) -> result.setStatus(ApiStatusEnum.SUCCESS).setResult(flag))
                .flatMap(result -> authorizeActionCallback.loginCallback().thenReturn(result))
                .onErrorResume(e -> Mono.error(new ApplicationException(e)));
        */
    }

    /**
     * 退出登录
     */
    public Mono<ApiResult<Boolean>> logout() {
        return ApiResult.<Boolean>create()
                // 假如客户端在会话对象有效期内调用注销接口时，传递了一个失效的token，
                // 后端执行注销操作的结果反馈是false；只有后端真正执行了注销操作才会返回true；
                .zipWith(sessionService.removeLocalSession(),
                        (apiResult, flag) -> apiResult.setStatus(ApiStatusEnum.SUCCESS).setResult(flag))
                .flatMap(apiResult -> {
                    // 需要特别说明，退出登录接口正常注销会话则主动将客户端Cookie删除；
                    // 如果后端会话自动过期失效，则不对客户端Cookie做任何处理！
                    if (apiResult.getResult()) {
                        // 在有效期内的会话对象被成功注销后才会执行其后续的注销逻辑，此时返回true
                        return authorizeActionCallback.logoutCallback().thenReturn(apiResult);
                    } else {
                        // 调用退出接口时，如果请求头参数token已经自动过期，实际并非主动执行注销的逻辑，此时返回false
                        return Mono.just(apiResult);
                    }
                })
                .onErrorResume(e -> Mono.error(new ApplicationException(e)));
    }

    /**
     * 获取当前登录用户
     */
    public Mono<ApiResult<AuthLoginBO>> current() {
        return ApiResult.<AuthLoginBO>create()
                .zipWith(sessionService.readLocalSession(),
                        (apiResult, authLoginBO) -> apiResult.setStatus(ApiStatusEnum.SUCCESS).setResult(authLoginBO))
                .onErrorResume(e -> Mono.error(new ApplicationException(e)));
    }

}
